package cn.sc.summer.core.config.security;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.sc.summer.constant.mybatisplus.TenantInfo;
import cn.sc.summer.constant.project.ProjectConstant;
import cn.sc.summer.constant.token.AuthProperties;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.MatchUtil;
import cn.sc.summer.core.util.SpringSecurityUserUtil;
import cn.sc.summer.exception.hodler.SpringContextHolder;
import cn.sc.summer.mybatis.context.TenantContextHolder;
import cn.sc.summer.redis.util.RouteUtil;
import cn.sc.summer.token.base.DefaultLoginToken;
import cn.sc.summer.token.constant.APIConstant;
import cn.sc.summer.token.encrypt.TokenUtil;
import cn.sc.summer.token.enums.RequestTypeEnum;
import cn.sc.summer.token.po.UserDetailX;
import cn.sc.summer.token.po.UserTokenInfo;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * 类名：校验用户访问资源是否合法且有权限
 *
 * @author a-xin
 * @date 2024/9/18 17:31
 */
@Slf4j
@Component
public class AuthorizationServerManagerX implements AuthorizationManager<RequestAuthorizationContext> {

    @Override
    public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {
        AuthorizationDecision decision = check(authentication, requestAuthorizationContext);
        if (decision != null && !decision.isGranted()) {
            Boolean check = authorizationCheck(requestAuthorizationContext.getRequest());
            if (!check) {
                throw new AccessDeniedException("Access Denied");
            }
        }
    }

    /**
     * Determines if access is granted for a specific authentication and object.
     *
     * @param authentication              the {@link Supplier} of the {@link Authentication} to check
     * @param requestAuthorizationContext the {@link T} object to check
     * @return an {@link AuthorizationDecision} or null if no decision could be made
     */
    @Override
    public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext requestAuthorizationContext) {

        HttpServletRequest request = requestAuthorizationContext.getRequest();
        SpringContextHolder.setHttpServletRequest(request);

        String requestURI = request.getRequestURI();
        String rawPath = RouteUtil.getRawPath(ProjectConstant.SERVER_NAME, requestURI);
        if (CharSequenceUtil.isBlank(rawPath)) {
            return new AuthorizationDecision(false);
        }
        log.info("-> resource path : {}", rawPath);

        AuthProperties authProperties = SpringUtil.getBean(AuthProperties.class);
        if (MatchUtil.anyMatchValue(rawPath, APIConstant.SWAGGER_SOURCE)
                || MatchUtil.anyMatchValue(rawPath, authProperties.getAuthIgnore())) {
            return new AuthorizationDecision(true);
        }

        String apiKey = request.getHeader(TokenConstant.API_KEY_REQUEST_HEADER);
        String requestType = request.getHeader(TokenConstant.REQUEST_TYPE);

        if (StrUtil.isBlank(apiKey) || StrUtil.isBlank(requestType)) {
            return new AuthorizationDecision(false);
        }

        Optional<RequestTypeEnum> byType = Optional.ofNullable(RequestTypeEnum.getByType(requestType));
        if (!byType.isPresent()) {
            return new AuthorizationDecision(false);
        }

        Boolean tokenCheck = TokenUtil.analysisApiEncryptKey(apiKey, byType.get());
        if (!tokenCheck) {
            return new AuthorizationDecision(false);
        }

        return new AuthorizationDecision(true);
    }

    /**
     * 请求token校验
     *
     * @param request 请求体
     * @return 是否通过校验
     */
    protected Boolean authorizationCheck(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String rawPath = RouteUtil.getRawPath(ProjectConstant.SERVER_NAME, requestURI);

        AuthProperties authProperties = SpringUtil.getBean(AuthProperties.class);
        if (MatchUtil.anyMatchValue(rawPath, APIConstant.SWAGGER_SOURCE)
                || MatchUtil.anyMatchValue(rawPath, authProperties.getAuthIgnore())) {
            return Boolean.TRUE;
        }

        if (!SpringSecurityUserUtil.hasLoginStatus()) {
            return Boolean.FALSE;
        }

        UserDetailX userDetailX = SpringSecurityUserUtil.getLoginUser();
        UserTokenInfo loginInfo = SpringSecurityUserUtil.getLoginInfo();
        String authenticationStr = loginInfo.getAuthentication();
        Authentication userAuthentication = JSONObject.parseObject(authenticationStr, Authentication.class);

        //获取权限信息，封装到Authentication中，存入SecurityContextHolder
        DefaultLoginToken authenticationToken = new DefaultLoginToken(userAuthentication.getPrincipal(),
                userAuthentication.getCredentials(),
                userDetailX.getAuthorities());
        authenticationToken.setUserDetailX(userDetailX);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        TenantInfo tenantInfo = new TenantInfo();
        //排除需要校验token的，否则会token种获取租户ID
        if (MatchUtil.anyMatchValue(rawPath, authProperties.getAuthIgnore())
                || MatchUtil.anyMatchValue(rawPath, authProperties.getTenantIgnore())) {
            tenantInfo.setIgnoreTenant(Boolean.TRUE);
            TenantContextHolder.setTenantInfo(tenantInfo);
            return Boolean.TRUE;
        }

        String loginUserTenantId = SpringSecurityUserUtil.getLoginUserTenantId();
        tenantInfo.setIgnoreTenant(Boolean.FALSE);
        tenantInfo.setTenantId(loginUserTenantId);
        TenantContextHolder.setTenantInfo(tenantInfo);

        return Boolean.TRUE;
    }

}
